Cognitoで発行されたJWTを検証する
準備
必要なパッケージをインストールします。
$ pip3 install pyjwt pyjwt[crypto]
pyjwt[crypto]
はJWTの検証に使用する鍵を作成するのに使います。
実装
最終的なコードは以下のようになります。
import jwt token = 'XXXXXXXX' region = 'ap-northeast-1' user_pool_id = 'ap-northeast-1_XXXXXXXX' client_id = 'XXXXXXXX' issuer = f'https://cognito-idp.{region}.amazonaws.com/{user_pool_id}' jwks_url = f'{issuer}/.well-known/jwks.json' jwks_client = jwt.PyJWKClient(jwks_url) signing_key = jwks_client.get_signing_key_from_jwt(token) token = jwt.decode( token, signing_key.key, algorithms=["RS256"], audience=client_id, issuer=issuer ) if token['token_use'] != 'id': raise Exception('Invalid token_use')
流れとしては以下のような感じです。
- 署名の検証に使用する鍵を作成
- デコードしつつ検証
以下で順に説明していきます。
今回トークンはAmplifyで取得したものを使用しています。
署名の検証に使用する鍵を作成
region = 'ap-northeast-1' user_pool_id = 'ap-northeast-1_XXXXXXXX' client_id = 'XXXXXXXX' issuer = f'https://cognito-idp.{region}.amazonaws.com/{user_pool_id}' jwks_url = f'{issuer}/.well-known/jwks.json' jwks_client = jwt.PyJWKClient(jwks_url) signing_key = jwks_client.get_signing_key_from_jwt(token)
ここではJSON Web Key(JWK)を読み込んで署名を検証する用の鍵を作成します。
JWKはユーザープールに紐づいており、jwks_url
から取得できます。
{ "keys": [ { "alg": "RS256", "e": "AQAB", "kid": "XXXXXXXX", "kty": "RSA", "n": "XXXXXXXX", "use": "sig" }, { "alg": "RS256", "e": "AQAB", "kid": "XXXXXXXX", "kty": "RSA", "n": "XXXXXXXX", "use": "sig" } ] }
JWKは上のような中身になっており、JWTのヘッダーに含まれるkid
と一致するようなものがここから選ばれます。
デコードしつつ検証
token = jwt.decode( token, # JWT signing_key.key, # 署名を検証するための鍵 algorithms=["RS256"], # 署名のアルゴリズム audience=client_id, # 利用者 issuer=issuer # トークンの発行者 ) if token['token_use'] != 'id': raise Exception('Invalid token_use')
PyJWTではデコードする際に同時に検証ができます。 このパラメータだと以下のクレームを検証してくれます。 検証に失敗するとエラーが発生します。
exp
iat
nbf
aud
iss
Cognitoのドキュメントによると以下のものを検証するのが推奨みたいです。
- トークンの有効期限(
exp
) - 利用者(
aud
) - 発行者(
iss
) - トークンの使用用途(
token_use
)
ここでは上3つまではPyJWTで検証できたので、最後のものは自分で検証します。
今回はIDトークンとして使用しているのでtoken_use
がid
となることを想定しています。
ここはアクセストークンとして使用するならaccess
となります。
algorithms
についてはハードコードするのが安全みたいです。
デコード後はペイロードがdict
として返されます。
最後に
PyJWTを使用することでJWTの検証を行うことができました。 Flaskアプリケーションなど自分で認証用のコードを書く必要がある時に便利だと思います。